#

import json
import re


class RuleError(Exception):
    def __init__(self, err):
        super().__init__(self)
        self.errorinfo = err

    def __str__(self):
        return self.errorinfo


class Validator(object):
    def __init__(self, data, rules):
        if not isinstance(data, dict):
            raise TypeError('data must be dict')
        if not isinstance(rules, dict):
            raise TypeError('rules must be dict')
        self.data = data
        self.rules = rules
        self.errors = {}
        self.jump = False

    def clear_data(self):
        result = {}
        valid_field = list(self.rules)
        for field in valid_field:
            self.jump = False
            rules = self.rules.pop(field)
            if isinstance(rules, list):
                pass
            elif isinstance(rules, str):
                rules = rules.split('|')
            else:
                raise RuleError(f'Invalid rule {rules}, need str or list')
            for rule in rules:
                if self.jump:
                    break
                if ':' in rule:
                    rule, args = rule.split(':')
                else:
                    args = None
                if hasattr(self, f'is_{rule}'):
                    getattr(self, f'is_{rule}')(field, args)
        result.update(self.data)
        return result

    def is_alpha(self, field, args=None):
        value = self.data.get(field)
        emsg = None
        try:
            if not re.match('^[a-zA-Z]+$', value):
                emsg = f'{field} type error, must be alpha'
        except Exception:
            emsg = f'{field} type error, must be alpha'
        self.push_err(field, emsg)

    def is_alpha_num(self, field, args=None):
        value = self.data.get(field)
        emsg = None
        try:
            if not re.match('^[a-zA-Z0-9]+$', value):
                emsg = f'{field} type error, only contain alpha-numeric'
        except Exception:
            emsg = f'{field} type error, only contain alpha-numeric'
        self.push_err(field, emsg)

    def is_boolean(self, field, args=None):
        value = self.data.get(field)
        emsg = None
        if not isinstance(value, bool):
            self.data[field] = False
            emsg = f'{field} type error, must be boolean'
        self.push_err(field, emsg)

    def is_email(self, field, args=None):
        value = self.data.get(field)
        emsg = None
        rex = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
        try:
            if not re.match(rex, value):
                emsg = f'{field} type error, must be email'
        except Exception:
            emsg = f'{field} type error, must be email'
        self.push_err(field, emsg)

    def is_select(self, field, args):
        value = self.data.get(field)
        emsg = None
        if value not in args.split(','):
            emsg = f"{field} value error, not in select {args.split(',')}"
        self.push_err(field, emsg)

    def is_max(self, field, args):
        value = self.data.get(field)
        emsg = None
        if isinstance(value, (int, float)):
            if value > float(args):
                emsg = f'{field} value {value} must <= {args}'
        elif isinstance(value, str):
            if len(value) > float(args):
                emsg = f'{field} length {len(value)} must <= {args}'
        else:
            emsg = f'{field} type error, must be number or str'
        self.push_err(field, emsg)

    def is_min(self, field, args):
        value = self.data.get(field)
        emsg = None
        if isinstance(value, (int, float)):
            if value < float(args):
                emsg = f'{field} value {value} must >= {args}'
        elif isinstance(value, str):
            if len(value) < float(args):
                emsg = f'{field} length {len(value)} must >= {args}'
        else:
            emsg = f'{field} type error, must be number or str'
        self.push_err(field, emsg)

    def is_null(self, field, args):
        value = self.data.get(field)
        emsg = None
        if args == 'false':
            if value is None or not value or not str(value).strip():
                emsg = f'{field} value error, can not be space'
        self.push_err(field, emsg)

    def is_number(self, field, args=None):
        value = self.data.get(field)
        emsg = None
        try:
            self.data[field] = int(value)
        except Exception:
            emsg = f'{field} type error, must be number'
        self.push_err(field, emsg)

    def is_numeric(self, field, args=None):
        value = self.data.get(field)
        emsg = None
        try:
            self.data[field] = int(value)
        except Exception:
            try:
                self.data[field] = float(value)
            except Exception:
                emsg = f'{field} type error, must be str or number'
        self.push_err(field, emsg)

    def is_required(self, field, args=None):
        self.is_null(field, 'false')
        value = self.data.get(field)
        emsg = None
        if not value:
            emsg = f'{field} required'
        self.push_err(field, emsg)

    def is_size(self, field, args):
        value = self.data.get(field)
        emsg = None
        if isinstance(value, (int, float)):
            if value != float(args):
                emsg = f'{field} value {value} must == {args}'
        elif isinstance(value, str):
            if len(value) != float(args):
                emsg = f'{field} length {len(value)} must == {args}'
        else:
            emsg = f'{field} type error, must be number or str'
        self.push_err(field, emsg)

    def is_sometime(self, field, args=None):
        if field not in self.data:
            self.jump = True

    def is_str(self, field, args=None):
        value = self.data.get(field)
        emsg = None
        if isinstance(value, (dict, list, tuple)):
            self.data[field] = json.dumps(value)
        elif isinstance(value, set):
            self.data[field] = json.dumps(list(value))
        else:
            try:
                self.data[field] = str(value)
            except Exception:
                emsg = f'{field} type error, must be str'
        self.push_err(field, emsg)

    def is_strip(self, field, args=None):
        value = self.data.get(field)
        emsg = None
        if isinstance(value, str):
            self.data[field] = value.strip()
        else:
            emsg = f'{field} type error, must be str'
        self.push_err(field, emsg)

    def is_special(self, field, args=None):
        value = self.data.get(field)
        emsg = None
        try:
            if not re.search('[#$%&*+/<=>?@^_-]+', value):
                emsg = f'{field} must contain one of special characters: #$%&*+/<=>?@^_-'
        except Exception:
            emsg = f'{field} must contain one of special characters: #$%&*+/<=>?@^_-'
        self.push_err(field, emsg)

    def push_err(self, field, emsg):
        if emsg:
            if not self.errors.get(field):
                self.errors[field] = []
            self.errors[field].append(emsg)


if __name__ == "__main__":
    data = {
        'username': 'Jack1',
        'age': '18',
        'email': '1421@2q.com',
        'sex': '1'
    }
    rules = {
        'username': 'required|alpha_num',
        'age': 'number',
        'sex': 'required',
        'email': 'email'
    }
    v = Validator(data, rules)
    data = v.clear_data()
    if not v.errors:
        print(data)
    else:
        print(v.errors)
